const std = @import("std");
const builtin = @import("builtin");
const csr = @import("riscv").csr;
const options = @import("options");
const main = @import("main.zig").main;

export var os_stacks: [os.NCPU][os.STACK_PER_CPU]u8 align(16) = undefined;

// QEMU/SBI passes hartid (a0) and FDT address (a1)
// https://github.com/qemu/qemu/blob/v10.0.0/hw/riscv/boot.c#L443-L463
pub export fn _entry() linksection(".kernel_entry") callconv(.naked) noreturn {
    // Saves them in tp (hartid) and gp (FDT) before stack setup.
    asm volatile ("mv tp, a0");
    asm volatile ("mv gp, a1");

    // set up stacks
    // sp = &os_stacks[0] + ((hartid + 1) * STACK_PER_CPU)
    asm volatile (std.fmt.comptimePrint(
            \\ la sp, os_stacks
            \\ li t0, {0}
            \\ addi t1, a0, 1
            \\ mul t0, t0, t1
            \\ add sp, sp, t0
        , .{os.STACK_PER_CPU}));

    // jump to start()
    asm volatile ("j start");
}

export fn start() void {
    if (options.m_mode) {
        // set M Previous Privilege mode to Supervisor, for mret.
        csr.mstatus.clear(.{ .mpp = .clear_mask });
        csr.mstatus.set(.{ .mpp = .supervisor });

        // set M Exception Program Counter to main, for mret.
        // requires code_model = .medium
        csr.mepc.write(&main);
    }

    // disable paging for now.
    csr.satp.set(.{ .mode = .none });

    if (options.m_mode) {
        // delegate all interrupts and exceptions to supervisor mode.
        csr.medeleg.write(0xffff);
        csr.mideleg.write(0xffff);
    }
    csr.sie.set(.{ .stie = true, .seie = true });

    if (options.m_mode) {
        // configure Physical Memory Protection to give supervisor mode
        // access to all of physical memory.
        csr.pmpaddr0.write(os.mem.vms.VirtAddr.MAXVA - 1);
        csr.pmpcfg0.write(0xf);

        // enable supervisor-mode timer interrupts.
        csr.mie.set(.{ .stie = true });

        // enable the sstc extension (i.e. stimecmp).
        csr.menvcfg.set(.{ .stce = true });

        // allow supervisor to use stimecmp and time.
        csr.mcounteren.set(.{ .tm = true });
    }

    // ask for the very first timer interrupt.
    csr.stimecmp.write(csr.time.read() + os.arch.qemu.TIMER_TICK_INTERVAL);

    if (options.m_mode) {
        // switch to supervisor mode and jump to main().
        asm volatile ("mret");
    } else {
        main();
    }
}

pub const os = @import("os.zig");
pub const panic = os.debug.panic;
pub const std_options: std.Options = .{
    .logFn = os.debug.log, // Overrides `std.log`.
    .log_scope_levels = &.{.{
        .scope = .syscall,
        .level = if (builtin.strip_debug_info) .err else std.log.default_level,
    }},
    .page_size_min = os.PGSIZE,
    .page_size_max = os.PGSIZE,
};
